<template>
    <div
        class="markdown-it-container"
        :style="{'-webkit-line-clamp': lineClamp}"
    >
        <div
            ref="markdownBodyRef"
            class="markdown-body"
            :class="[themeComputed]"
            @click="handleClick($event)"
            v-html="markdownHtml"
        ></div>

        <div v-if="typing" class="markdown-typing" :style="cursorPosition"></div>
    </div>
</template>

<script lang="ts" setup>
import { createMarkdown, preprocessContent } from './markdownParser'
import { useDark , useClipboard } from '@vueuse/core'
import feedback from '@/utils/feedback'
import 'github-markdown-css'
import 'katex/dist/katex.min.css'
import 'highlight.js/styles/atom-one-dark.css'

const props = withDefaults(
    defineProps<{
        // 文本内容
        content: string;
        // 是否允许HTML标签
        html?: boolean;
        // 是否显示模拟打字机效果
        typing?: boolean;
        // 是否将换行符转换为 <br> 标签
        breaks?: boolean;
        // 是否自动将 URL 转换为链接
        linkify?: boolean;
        // 是否启用排版优化,如智能引号等
        typographer?: boolean;
        // 是否显示行号
        lineNumbers?: boolean;
        // 显示的文本行数限制,超过的行会被截断
        lineClamp?: number;
        // 自定义链接列表,用于替换或添加链接
        linkList?: any[]
        // 主题颜色设置
        theme?: 'light' | 'dark';
    }>(),
    {
        html: true,
        typing: false,
        breaks: true,
        linkify: true,
        typographer: true,
        lineNumbers: true,
        lineClamp: 0,
        linkList: () => []
    }
)

const emits = defineEmits<{
    (event: 'click-custom-link', value: string): void
}>()

const { copy } = useClipboard()
const isDark = useDark()
const markdownBodyRef = shallowRef<HTMLDivElement>()

// markdown内容
const markdownHtml = ref('')

// 光标位置参数
const cursorPosition = reactive({
    top: 'auto',
    left: 'auto'
})

// 主题风格参数
const themeComputed = computed(() => {
    if (props.theme) {
        return props.theme
    }
    return isDark.value ? 'dark' : 'light'
})

// 创建Markdown
let md = createMarkdown({
    html: props.html,
    breaks: props.breaks,
    linkify: props.linkify,
    typographer: props.typographer,
    lineNumbers: props.lineNumbers
})

/**
 * 查找最后一个文本节点
 */
const findLastTextNode = (parent: Node): Node | undefined => {
    const children = parent.childNodes
    for (let i = children.length - 1; i >= 0; i--) {
        const node = children[i]
        if (node.nodeType === Node.TEXT_NODE && /\S/.test(node.nodeValue!)) {
            node.nodeValue?.replace(/\S+$/, '')
            return node
        } else if (node.nodeType === Node.ELEMENT_NODE) {
            const lastNode = findLastTextNode(node)
            if (lastNode) {
                return lastNode
            }
        }
    }
}

/**
 * 更新光标显示的位置
 */
const updateCursor = () => {
    if (!props.typing) {
        return
    }
    if (markdownBodyRef.value) {
        const lastTextNode = findLastTextNode(markdownBodyRef.value)
        const textNode = document.createTextNode('\u200B')
        if (lastTextNode) {
            lastTextNode.parentElement?.appendChild(textNode)
        } else {
            markdownBodyRef.value?.appendChild(textNode)
        }
        const range = document.createRange()
        range.setStart(textNode, 0)
        range.setEnd(textNode, 0)
        const textRect = range.getBoundingClientRect()
        const markdownBodyRect = markdownBodyRef.value?.getBoundingClientRect()
        cursorPosition.left = `${textRect.left - markdownBodyRect.left}px`
        cursorPosition.top = `${textRect.top - markdownBodyRect.top}px`
        textNode.remove()
    }
}

/**
 * 处理点击事件
 */
const handleClick = async (e: any) => {
    const target: HTMLElement = e.target
    if (target.className === 'code-copy-btn') {
        const text = e.target.nextElementSibling.textContent
        try {
            await copy(text)
            feedback.msgSuccess('复制成功')
        } catch {
            feedback.msgSuccess('复制失败')
        }
    }
    if (target.className === 'markdown-custom-link') {
        emits('click-custom-link', target.textContent!)
    }
}

watchEffect(
    async () => {
        md = createMarkdown({
            html: props.html,
            breaks: props.breaks,
            linkify: props.linkify,
            typographer: props.typographer,
            lineNumbers: props.lineNumbers
        })
        markdownHtml.value = md?.render(preprocessContent(props.content), {
            linkList: props.linkList
        })
        await nextTick()
        updateCursor()
    },
    {
        flush: 'post'
    }
)
</script>

<style lang="scss">
@use 'markdown';
</style>
